package edu.nus.cs4243.recon.ui;

import java.io.File;
import java.io.IOException;
import java.util.Set;

import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;

import com.jreiffers.uihandlers.EventHandler;
import com.jreiffers.uihandlers.UiHandler;

import Jama.Matrix;

import edu.nus.cs4243.recon.logic.Camera;
import edu.nus.cs4243.recon.logic.EssentialMatrixEstimator;
import edu.nus.cs4243.recon.logic.RotationTranslationEstimator;
import edu.nus.cs4243.recon.logic.Triangulator;
import edu.nus.cs4243.recon.logic.TwoViewReconstructor;
import edu.nus.cs4243.recon.model.Feature;
import edu.nus.cs4243.recon.model.HasCoordinates2d;
import edu.nus.cs4243.recon.model.Image;
import edu.nus.cs4243.recon.model.Point3d;
import edu.nus.cs4243.recon.model.models.Model;
import edu.nus.cs4243.recon.model.models.ObjModelWriter;
import edu.nus.cs4243.recon.model.project.Project;
import edu.nus.cs4243.recon.ui.preview.ModelPreview;

/**
 * The 3d reconstructor main window.
 * 
 * @author johannes
 */
public class MainView {

	private Camera currentCamera;
	private final Display display = new Display();
	private final Shell shell = new Shell(display);
	private final DropTarget dropTarget;

	private final Menu menuBar = new Menu(shell, SWT.BAR);
	private final Menu fileMenu = new Menu(shell, SWT.DROP_DOWN);
	private final Menu imageMenu = new Menu(shell, SWT.DROP_DOWN);
	private final MenuItem reconstructCurrent, printUndistortedCoordinates, openProject, newProject;

	private final ImageControl leftImageDisplay = new ImageControl(shell, SWT.BORDER);
	private final ImageControl rightImageDisplay = new ImageControl(shell, SWT.BORDER);
	private final List leftImageList = new List(shell, SWT.BORDER | SWT.V_SCROLL);
	private final List rightImageList = new List(shell, SWT.BORDER | SWT.V_SCROLL);
	
	/**
	 * Constructs a new Main window and shows it.
	 * 
	 * @throws IOException
	 */
	public MainView() throws IOException {
		currentCamera = new Camera(new File("data/int.35mm.txt"), new File("data/dist.35mm.txt"));

		dropTarget = new DropTarget(shell, DND.DROP_DEFAULT | DND.DROP_MOVE);
		dropTarget.setTransfer(new Transfer[] { FileTransfer.getInstance() });

		MenuItem it = new MenuItem(menuBar, SWT.CASCADE);
		it.setMenu(fileMenu);
		it.setText("&File");
		
		newProject = new MenuItem(fileMenu, SWT.PUSH);
		newProject.setText("New project ...");
		openProject = new MenuItem(fileMenu, SWT.PUSH);
		openProject.setText("Open project ...");
		
		it = new MenuItem(menuBar, SWT.CASCADE);
		it.setMenu(imageMenu);
		it.setText("&Image");

		reconstructCurrent = new MenuItem(imageMenu, SWT.PUSH);
		reconstructCurrent.setText("Reconstruct from current view");
		reconstructCurrent.setEnabled(false);

		printUndistortedCoordinates = new MenuItem(imageMenu, SWT.PUSH);
		printUndistortedCoordinates.setText("Print undistorted coordinates");
		printUndistortedCoordinates.setEnabled(false);

		shell.setMenuBar(menuBar);

		UiHandler.setup(this);
		shell.open();
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch())
				display.sleep();
		}
		display.dispose();
	}

	@EventHandler(value = "dropTarget")
	public void onDrop(DropTargetEvent event) {
		FileTransfer ft = FileTransfer.getInstance();
		if (ft.isSupportedType(event.currentDataType)) {
			String[] fileList = (String[]) event.data;
			for (String file : fileList) {
				try {
					handleAddFile(new File(file));
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

	@EventHandler(value = "printUndistortedCoordinates", event = SWT.Selection)
	public void onPrintUndistortedCoordinates(Event event) {
		printUndistortedCoordinates(leftImageDisplay.getVisibleImage());
		printUndistortedCoordinates(rightImageDisplay.getVisibleImage());
	}

	@EventHandler(value = "reconstructCurrent", event = SWT.Selection)
	public void onReconstructCurrentView(Event event) {
		try {
			final Image left = leftImageDisplay.getVisibleImage();
			final Image right = rightImageDisplay.getVisibleImage();
			
			int idx = 0;
			ModelPreview preview = new ModelPreview(display);
			for (Model m : TwoViewReconstructor.getPossibleModels(currentCamera, left, right, false)) {
				preview.addModel("Model " + (++idx), m);
				ObjModelWriter.inst.saveToFile(new File("model" + idx + ".obj"), m);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	@EventHandler(value = "shell", event = SWT.Resize)
	public void onResize(Event event) {
		final Rectangle clientArea = shell.getClientArea();
		leftImageList.setBounds(0, 0, 150, clientArea.height);
		rightImageList.setBounds(clientArea.width - 150, 0, 150, clientArea.height);
		leftImageDisplay.setBounds(150, 0, (clientArea.width - 300) / 2, clientArea.height);
		rightImageDisplay.setBounds(clientArea.width / 2, 0, (clientArea.width - 300) / 2,
				clientArea.height);
	}

	@EventHandler(value = { "leftImageList", "rightImageList" }, event = SWT.Selection)
	public void onSelectImage(Event event) {
		updateImages();
	}
	
	@EventHandler(value = "openProject", event = SWT.Selection)
	public void onOpenProject(Event event) {
		FileDialog fd = new FileDialog(shell, SWT.OPEN);
		fd.setFilterExtensions(new String[] { "*.3rp"});
		String path = fd.open();
		if (path == null)
			return;
		if (new File(path).exists()) {
			try {
				ImageControlGlobalState.getState().setProject(Project.load(new File(path)));
				for (File f : ImageControlGlobalState.getState().getProject().files()) {
					leftImageList.add(f.getName());
					rightImageList.add(f.getName());
				}
			} catch (IOException e) {
				MessageBox mb = new MessageBox(shell, SWT.ERROR | SWT.OK);
				mb.setText("Could not open file.");
				mb.setMessage("The file you selected is either corrupted or not a valid 3d-recon project file.");
				mb.open();

				e.printStackTrace();
			}
		}
	}
	
	@EventHandler(value = "newProject", event = SWT.Selection)
	public void onNewProject(Event event) {
		FileDialog fd = new FileDialog(shell, SWT.SAVE);
		fd.setFilterExtensions(new String[] { "*.3rp"});
		fd.setOverwrite(true);
		String path = fd.open();
		if (path == null)
			return;
		ImageControlGlobalState.getState().setProject(new Project(new File(path)));
	}
	
	/**
	 * Called by the drop-handler for each new file.
	 * 
	 * @param file
	 */
	protected void handleAddFile(File file) throws IOException {
		if (ImageControlGlobalState.getState().getProject() == null) {
			onNewProject(null);
			if (ImageControlGlobalState.getState().getProject() == null) {
				return;
			}
		}
		
		ImageControlGlobalState.getState().getProject().files().add(file);
		ImageControlGlobalState.getState().getProject().save();

		leftImageList.add(file.getName());
		rightImageList.add(file.getName());
	}

	/**
	 * Print the undistorted coordinates of an image
	 * 
	 * @param i
	 *            the image whose features' coordinates to undistort and print.
	 */
	protected void printUndistortedCoordinates(Image i) {
		if (i != null) {
			for (Feature f : i.getFeatures()) {
				HasCoordinates2d original = i.getLocationInView(f);
				HasCoordinates2d undist = currentCamera.undistort(original);
				System.out.println(original + " -> " + undist);
			}
		}
	}

	/**
	 * <p>
	 * Set the selected image of both image controls and causes them to be redrawn.
	 * </p>
	 * <p>
	 * <code>updateImages</code> calls updateImage for both image lists (leftImageList and
	 * rightImageList) and both image displays.
	 * </p>
	 */
	protected void updateImages() {
		updateImage(leftImageList, leftImageDisplay);
		updateImage(rightImageList, rightImageDisplay);
		reconstructCurrent.setEnabled(leftImageDisplay.getVisibleImage() != null
				&& rightImageDisplay.getVisibleImage() != null);
		printUndistortedCoordinates.setEnabled(leftImageDisplay.getVisibleImage() != null
				|| rightImageDisplay.getVisibleImage() != null);
	}

	/**
	 * <p>
	 * Set the currently selected image of an ImageControl to the file that is currently selected in
	 * an image list.
	 * </p>
	 * <p>
	 * If no item is selected in the image list, the visible image of the image display will be set
	 * to null.
	 * </p>
	 * 
	 * @param imgList
	 *            the image list whose selection to display
	 * @param imgDisplay
	 *            the display to show the image in
	 */
	private void updateImage(List imgList, ImageControl imgDisplay) {
		int idx = imgList.getSelectionIndex();
		if (idx < 0) {
			imgDisplay.setVisibleImage(null);
		} else {
			File newPath = ImageControlGlobalState.getState().getProject().files().get(idx);
			if (imgDisplay.getVisibleImage() == null || !imgDisplay.getVisibleImage().getPath().equals(newPath)) {
				try {
					imgDisplay.setVisibleImage(new Image(newPath));
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			}
		}
		imgDisplay.redraw();
	}

}
